#include "semantic.h"

int structTmpNameNum = 0;
attri* intAttri = NULL;
attri* floatAttri = NULL;

void printSemMsg(int errid, int lineno, const char* msg) {
    fprintf(stderr, "Error type %d at Line %d: ", errid, lineno);
    if (msg) {
        fprintf(stderr, semErrMsg[errid], msg);
    } else {
        fprintf(stderr, semErrMsg[errid]);
    }
    fprintf(stderr, ".\n");
}

bool cmp(treenode* node, const char* str)
{
    if(!node) return false;
    return strcmp(node->typename, str) == 0;
}

void parseProgram(treenode* node)
{   /* Program: ExtDefList */
    assert(node->capacity == 1 && strcmp(node->next[0]->typename,"ExtDefList") == 0);
    parseExtDefList(node->next[0]);
}

void parseExtDefList(treenode* node)
{   /* ExtDefList: ExtDef ExtDefList
        | empty */
    if(node->capacity == 0) /* 2 */
        return;
    assert(node->capacity == 2);
    parseExtDef(node->next[0]);
    parseExtDefList(node->next[1]);
}

void parseExtDef(treenode* node)
{   /* ExtDef: Specifier ExtDecList SEMI
        | Specifier SEMI
        | Specifier FunDec CompSt */
    attri* val = parseSpecifier(node->next[0]); /* 1 2 3 */
    if(!val) return;
    treenode* next1 = node->next[1];
    if(cmp(next1, "ExtDecList")) /* 1 */
        parseExtDecList(next1, val);
    else if(cmp(next1, "FunDec")) /* 3 */
    {
        mempara* para = NULL;
        bool error = false;
        para = parseFunDec(next1, val, &error);
        if(error) return;
        assert(node->next[2] != NULL);
        parseCompSt(node->next[2], para, val);
    }
    else if(cmp(next1, "SEMI")){}
    else
        assert(0);
    freeAttri(val);
}

attri* parseSpecifier(treenode* node)
{   /* Specifier: TYPE
        | StructSpecifier */
    treenode* next0 = node->next[0];
    if(cmp(next0,"TYPE")) /* 1 */
    {
        attri* ret = NULL;
        if(strcmp(next0->val, "int") == 0)
            ret = newBaseAttri(INT);
        else if(strcmp(next0->val, "float") == 0)
            ret = newBaseAttri(FLOAT);
        else
            assert(0);
        return ret;
    }
    else if(cmp(next0,"StructSpecifier")) /* 2 */
        {return parseStructSpecifier(next0);}
    else
        assert(0);
}

void parseExtDecList(treenode* node, attri* val)
{   /* ExtDecList: VarDec
        | VarDec COMMA ExtDecList */
    if(!val) return;
    treenode* next0 = node->next[0];
    parseDefVarDec(next0, val); /* 1 2 */
    if(node->capacity == 3) /* 2 */
    {
        treenode* next2 = node->next[2];
        parseExtDecList(next2, val);
    }
}

mempara* parseFunDec(treenode* node, attri* val, bool* error)
{   /* FunDec: ID LP VarList RP
        | ID LP RP */
    if(!val) return NULL;
    treenode* next0 = node->next[0];
    char* funcName = (char *)malloc((strlen(next0->val) + 1) * sizeof(char));
    strcpy(funcName, next0->val);
    if(isDupName(funcName))
    {
        free(funcName);
        printSemMsg(RE_FUNC, next0->line, next0->val);
        *error = true;
        return NULL;
    }
    mempara* paraAttri = NULL;
    attri* funcAttri = NULL;
    if(node->capacity == 3) /* 2 */
    {
        funcAttri = newFuncAttri(copyAttri(val),paraAttri);
    }
    else if(node->capacity == 4) /* 1 */
    {
        treenode* next2 = node->next[2];
        assert(cmp(next2,"VarList"));
        parseVarList(next2,&paraAttri);
        funcAttri = newFuncAttri(copyAttri(val),paraAttri);
    }
    else
        assert(0);
    insertSymbol(funcName, funcAttri);
    return paraAttri;
}

void parseCompSt(treenode* node, mempara* paraAttri, attri* retAttri)
{   /* CompSt: LC DefList StmtList RC */
    assert(node->capacity == 4);
    newTable();
    mempara* tmp = paraAttri;
    while(tmp != NULL)
    {
        insertSymbol(tmp->name, copyAttri(tmp->val));
        tmp = tmp->link;
    }
    parseDefList(node->next[1]);
    parseStmtList(node->next[2], retAttri);
    deleteTable();
}

attri* parseStructSpecifier(treenode* node) /* TODO */
{   /* StructSpecifier: STRUCT OptTag LC DefList RC
        | STRUCT Tag */
    attri* val = NULL;
    char* key = NULL;
    if(node->capacity == 5) /* 1 */
    {
        key = parseOptTag(node->next[1]);
        if(isStructdefDupName(key))
        {
            printSemMsg(RE_STRUCT, node->next[1]->line,key);
            return NULL;
        }
        /* TODO: check struct name duplicated or not */
        mempara* memAttri = NULL;
        parseStructDefList(node->next[3], &memAttri);
        attri* defval = newStructDefAttri(memAttri);
        insertSymbol(key, defval);
    }
    else if(node->capacity == 2) /* 2 */
    {
        key = parseTag(node->next[1]);
        map_t* target = allTableSearch(key);
        if(!target)
        {
            printSemMsg(UN_STRUCT, node->next[1]->line, key);
            return NULL;
        }
        attri* structAttri = (attri*)(target->val);
        if (structAttri->type != STRUCTDEF) 
        {
            printSemMsg(NOT_STRUCT, node->next[1]->line, key);
            return NULL;
        }
    }
    else
        assert(0);
    map_t* structDefSymbol = allTableSearch(key);
    if(structDefSymbol)
        val = newStructAttri(structDefSymbol);
    else
        assert(0);
    free(key);
    return val;
}

void parseStructDefList(treenode* node, mempara** memAttri)
{   /* DefList: Def DefList
         empty */
    if(node->capacity == 2) /* 1 */
    {
        parseStructDef(node->next[0], memAttri);
        parseStructDefList(node->next[1], memAttri);
    }
    else if(node->capacity == 0) /* 2 */
    {}
    else
        assert(0);
}

void parseStructDef(treenode* node, mempara** memAttri)
{   /* Def: Specifier DecList SEMI */
    attri* val = parseSpecifier(node->next[0]);
    if(!val) return NULL;
    parseStructDecList(node->next[1], val, memAttri);
    freeAttri(val);
}

void parseStructDecList(treenode* node, attri* val, mempara** memAttri)
{   /* DecList: Dec
        | Dec COMMA DecList */
    if(!val) return;
    parseStructDec(node->next[0], val, memAttri); /* 1 2 */
    if(node->capacity == 3) /* 2 */
        parseStructDecList(node->next[2], val, memAttri);
}

void parseStructDec(treenode* node, attri* val, mempara** memAttri)
{   /* Dec: VarDec
        | VarDec ASSIGNOP Exp */
    if(!val) return;
    parseStructVarDec(node->next[0], val, memAttri); /* 1 2 */
    if(node->capacity == 3)
    {
        printSemMsg(RE_FIELD, node->next[0]->line, "");
        return;
        attri* expAttri = parseExp(node->next[2]);
        if(expAttri && !attriEqual(val, expAttri))
        {
            printSemMsg(AS_TYPE, node->next[1]->line, NULL);
            return;
        }
    }
    /* TODO: assign error process */

}

void parseStructVarDec(treenode* node, attri* val, mempara** memAttri)
{   /* VarDec: ID
        | VarDec LB INT RB */
    if(!val) return NULL;
    mempara* paraAttri = NULL;
    if(node->capacity == 1) /* 1 */
    {
        if(isDupInMempara(node->next[0]->val, *memAttri) || isMemparaDupName(node->next[0]->val))
        {
            printSemMsg(RE_FIELD, node->next[0]->line, node->next[0]->val);
            return;
        }
        paraAttri = newMempara(node->next[0]->val, copyAttri(val));
        if(*memAttri)
        {
            mempara* tmp = *memAttri;
            while(tmp->link)
            {
                tmp = tmp->link;
            }
            tmp->link = paraAttri;
        }
        else
        {
            *memAttri = paraAttri;
        }
    }
    else if(node->capacity == 4) /* 2 */
    {
        attri* arrayTypeSplit = newArrayAttri(copyAttri(val), atoi(node->next[2]->val));
        parseStructVarDec(node->next[0], arrayTypeSplit, memAttri);
        freeAttri(arrayTypeSplit);
    }
    else
        assert(0);
}

bool isDupInMempara(char* name, mempara* mp)
{
    if(!mp) return false;
    mempara* tmp = mp;
    while(tmp)
    {
        if(strcmp(tmp->name, name) == 0)
            return true;
        tmp = tmp->link;
    }
    return false;
}

char* getStructTmpName()
{
    int tmp = structTmpNameNum;
    int len = 1;
    do{
        len++;
        tmp /= 10;
    }while(tmp != 0);
    char* ret = (char*)malloc(len);
    sprintf(ret, "%d", structTmpNameNum++);
    return ret;
}

char* parseTag(treenode* node)
{   /* Tag: ID */
    assert(cmp(node->next[0], "ID"));
    parseOptTag(node);
}

char* parseOptTag(treenode* node)
{   /* OptTag: ID
        | empty */
    if(node->capacity == 1) /* 1 */
    {
        assert(cmp(node->next[0], "ID"));
        int len = strlen(node->next[0]->val);
        char* ret = (char*)malloc((len+1) * sizeof(char));
        strcpy(ret, node->next[0]->val);
        return ret;
    }
    else if(node->capacity == 0) /* 2 */
        return getStructTmpName();
    else
        assert(0);

}

/* parse VarDec in define clause */
void parseDefVarDec(treenode* node, attri* val)
{   /* VarDec: ID
        | VarDec LB INT RB */
    if(!val) return;
    if(node->capacity == 1) /* 1 */
    {
        if(isDupName(node->next[0]->val))
        {
            printSemMsg(RE_VAR, node->next[0]->line, node->next[0]->val);
            return;
        }
        else
            insertSymbol(node->next[0]->val, copyAttri(val));
    }
    else if(node->capacity == 4) /* 2 */
    {
        attri* arrayTypeSplit = newArrayAttri(copyAttri(val), atoi(node->next[2]->val));
        parseDefVarDec(node->next[0], arrayTypeSplit);
    }
    else
        assert(0);
}

void parseVarList(treenode* node, mempara** paraAttri)
{   /* VarList: ParamDec COMMA VarList
        | ParmDec */
    treenode* next0 = node->next[0];
    parseParamDec(next0, paraAttri); /* 1 2 */
    if(node->capacity == 3) /* 1 */
    {
        parseVarList(node->next[2], paraAttri);
        /*if(para)
            para->link = parseVarList(node->next[2]);
        else
            para = parseVarList(node->next[2]);*/
    }
}

void parseDefList(treenode* node)
{   /* DefList: Def DefList
        |empty */
    if(node->capacity == 2) /* 1 */
    {
        parseDef(node->next[0]);
        parseDefList(node->next[1]);
    }
    else if(node->capacity == 0) /* 0 */
        return;
    else
        assert(0);

}

void parseStmtList(treenode* node, attri* retType)
{   /* StmtList: Stmt StmtList
        | empty */
    if(node->capacity == 2) /* 1 */
    {
        parseStmt(node->next[0], retType);
        parseStmtList(node->next[1], retType);
    }
    else if(node->capacity == 0) /* 2 */
        return;
    else
        assert(0);

}


void parseParamDec(treenode* node, mempara** paraAttri)
{   /* ParamDec: Specifier VarDec */
    attri* val = parseSpecifier(node->next[0]);
    if(!val) return;
    parseFuncVarDec(node->next[1], val, paraAttri);
    freeAttri(val);
}

/* parse VarDec in function parameters */
void parseFuncVarDec(treenode* node, attri* val, mempara** paraAttri)
{
    /* VarDec: ID
        | VarDec LB INT RB */
    if(!val) return NULL;
    mempara* para = NULL;
    if(node->capacity == 1) /* 1 */
    {
        if(isDupInMempara(node->next[0]->val, *paraAttri) || isMemparaDupName(node->next[0]->val))
        {
            printSemMsg(RE_VAR, node->next[0]->line, node->next[0]->val);
            return NULL;
        }
        para = newMempara(node->next[0]->val, copyAttri(val));
        if(*paraAttri)
        {
            mempara* tmp = *paraAttri;
            while(tmp->link)
            {
                tmp = tmp->link;
            }
            tmp->link = para;
        }
        else
        {
            *paraAttri = para;
        }
    }
    else if(node->capacity == 4) /* 2 */
    {
        attri* arrayTypeSplit = newArrayAttri(copyAttri(val), atoi(node->next[2]->val));
        parseFuncVarDec(node->next[0], arrayTypeSplit, paraAttri);
        freeAttri(arrayTypeSplit);
    }
    else
        assert(0);
}

void parseDef(treenode* node)
{   /* Def: Specifier DecList SEMI */
    attri* val = parseSpecifier(node->next[0]);
    if(!val) return;
    parseDecList(node->next[1], val);
    freeAttri(val);
}

void parseDecList(treenode* node, attri* val)
{   /* DecList: Dec
        | Dec COMMA DecList */
    if(!val) return;
    parseDec(node->next[0], val); /* 1 2 */
    if(node->capacity == 3) /* 2 */
        parseDecList(node->next[2], val);
}

void parseDec(treenode* node, attri* val)
{   /* Dec: VarDec
        | VarDec ASSIGNOP Exp */
    if(!val) return;
    parseDefVarDec(node->next[0], val);
    /* TODO: assign error process */
    if(node->capacity == 3)
    {
        attri* expAttri = parseExp(node->next[2]);
        if(expAttri && !attriEqual(val, expAttri))
        {
            printSemMsg(AS_TYPE, node->next[1]->line, NULL);
            return;
        }
    }

}

void parseStmt(treenode* node, attri* retType)
{   /* Stmt: Exp SEMI
        | CompSt
        | RETURN Exp SEMI
        | IF LP Exp RP Stmt
        | IF LP Exp RP Stmt ELSE Stmt
        | WHILE LP Exp RP Stmt */
    treenode* next0 = node->next[0];
    if(cmp(next0, "Exp")) /* 1 */
    {/* TODO: process Exp error */
        parseExp(next0);
    }
    else if(cmp(next0, "CompSt"))
        parseCompSt(node->next[0], NULL, retType);
    else if(cmp(next0, "RETURN"))
    {
        attri* tmp = parseExp(node->next[1]);
        if (tmp && !attriEqual(tmp, retType))
        {
            printSemMsg(RET_TYPE, node->next[1]->line, NULL);
        }    
    }
    else if(cmp(next0, "IF"))
    {   
        attri* tmp = parseExp(node->next[2]);
        if (tmp && tmp->type != INT)
            printSemMsg(NOT_INT, node->next[2]->line, NULL);
        parseStmt(node->next[4], retType);
        if(node->capacity == 7) parseStmt(node->next[6], retType);
    }
    else if(cmp(next0, "WHILE"))
    {
        attri* tmp = parseExp(node->next[2]);
        if (tmp && tmp->type != INT)
            printSemMsg(NOT_INT, node->next[2]->line, NULL);
        parseStmt(node->next[4], retType);
    }
    else
        assert(0);
}

bool isLeftValue(treenode* node) {
    // only [ID] [Exp LB Exp RB] [Exp DOT ID] can be leftValue
    if (node == NULL || strcmp(node->typename, "Exp") != 0) {
        // not Exp or is null
        return false;
    }
    if (node->capacity == 1 && strcmp(node->next[0]->typename, "ID") == 0) {
        // [ID]
        return true;
    } else if (node->capacity == 4 && strcmp(node->next[1]->typename,"LB") == 0) {
        // [Exp LB Exp RB]
        return isLeftValue(node->next[0]);
    } else if (node->capacity == 3 && strcmp(node->next[1]->typename, "DOT") == 0) {
        //[Exp DOT ID]
        return isLeftValue(node->next[0]);
    }
    return false;
}

attri* parseExp(treenode* node)
{   /* Exp: Exp ASSIGNOP Exp
        | Exp AND Exp
        | Exp OR Exp
        | Exp RELOP Exp
        | Exp PLUS Exp
        | Exp MINUS Exp
        | Exp STAR Exp
        | Exp DIV Exp
        | LP Exp RP
        | MINUS Exp 
        | NOT Exp
		| ID LP Args RP
		| ID LP RP
		| Exp LB Exp RB
		| Exp DOT ID
		| ID
		| INT
		| FLOAT */
    attri* val = NULL;
    treenode* next0 = node->next[0];
    treenode* next1 = NULL;
    if(node->capacity >= 2)
        next1 = node->next[1];
    treenode* next2 = NULL;
    if(node->capacity >= 3)
        next2 = node->next[2];
    if(node->capacity == 3 && cmp(next0,"Exp") && cmp(next2, "Exp")) /* 1-8 */
    {
        val = parseExp(next0);
        if(!val) return NULL;
        attri* val2 = parseExp(next2);
        if(!val2) return NULL;
        if(strcmp(node->next[1]->typename, "ASSIGNOP") == 0)
        {
            if(!attriEqual(val,val2))
            {
                printSemMsg(AS_TYPE, node->next[0]->line,NULL);
                return NULL;
            }
            else if(!isLeftValue(node->next[0]))
            {
                printSemMsg(AS_RVAL, node->next[0]->line, NULL);
                return NULL;
            }
        }
        else if(strcmp(next1->typename, "AND") == 0 || strcmp(next1->typename, "OR") == 0)
        {
            if (val->type != val2->type || val->type != INT) {
                printSemMsg(OP_TYPE, node->next[1]->line, NULL);
                return NULL;
            }
        }
        else if(strcmp(next1->typename, "PLUS") == 0 || strcmp(next1->typename, "MINUS") == 0 || strcmp(next1->typename, "STAR") == 0 || strcmp(next1->typename, "DIV") == 0)
        {
            if(val->type != val2->type || (val->type != INT && val->type != FLOAT))
            {
                printSemMsg(OP_TYPE, node->next[1]->line, NULL);
                return NULL;
            }
        }
        else if(strcmp(next1->typename, "RELOP") == 0)
        {
            if(val->type != val2->type || (val->type != INT && val->type != FLOAT))
            {
                printSemMsg(OP_TYPE, node->next[1]->line, NULL);
                return NULL;
            }
            else
                val = intAttri;
        }
        /* TODO: type match check */
    }
    else if(cmp(next0,"LP")) /* 9 */
    {
        val = parseExp(next1);
        if(!val) return NULL;
    }
    else if(node->capacity == 2) /* 10 11 */
    {
        val = parseExp(next1);
        if(!val) return NULL;
        if(strcmp(next0->typename, "MINUS") == 0)
        {
            if(val->type != INT && val->type != FLOAT)
            {
                printSemMsg(OP_TYPE, node->next[1]->line, NULL);
                return NULL;
            }
        }
        else if(strcmp(next0->typename, "NOT") == 0)
        {
            if(val->type != INT)
            {
                printSemMsg(OP_TYPE, node->next[1]->line, NULL);
                return NULL;
            }
        }
    }
    else if(cmp(next0, "ID")) /* 12 13 16 */
    {
        map_t* target = allTableSearch(next0->val);
        if(target)
        {   
            val = (attri*)(target->val);
            if(((attri*)(target->val))->type == FUNC)
            {
                if(node->capacity == 4) /* 12 */
                {
                    if(parseArgs(node->next[2], val->ufunc.paraAttri, next0->val))
                        val = ((attri*)(target->val))->ufunc.retType;
                    else
                        val = NULL;
                }
                else if(node->capacity == 3) /* 13 */
                {
                    if(((attri*)(target->val))->ufunc.paraNum == NULL)
                        val = ((attri*)(target->val))->ufunc.retType;
                    else
                        val = NULL;
                }
            }
            else if(node->capacity != 1)
            {
                printSemMsg(NOT_FUNC, node->next[0]->line, node->next[0]->val);
                return NULL;
            }
        }
        else
        { 
            /* TODO: process undefined */
            if(node->capacity == 1) /* 16 */
            {
                printSemMsg(UN_VAR, node->next[0]->line, node->next[0]->val);
                return NULL;
            }
            else /* 12 13 */
            {
                printSemMsg(UN_FUNC, node->next[0]->line, node->next[0]->val);
                return NULL;
            }
        }
    }
    else if(node->capacity == 4 && cmp(next0, "Exp")) /* 14 */
    {
        attri* arraySubVal = parseExp(next0);
        if(!arraySubVal) return NULL;
        /* TODO: not array type */
        if (arraySubVal->type != ARRAY) 
        {
            printSemMsg(NOT_ARR, node->next[0]->line, NULL);
            return NULL;
        }
        if(arraySubVal->uarray.subAttri)
            val = arraySubVal->uarray.subAttri;
        else
        { 
            assert(0);
        }

        attri* index = parseExp(next2);
        if(!index) return NULL;
        if(index->type != INT)
        {
            printSemMsg(NOT_INT, node->next[2]->line, NULL);
            return NULL;
        }
    }
    else if(node->capacity == 3 && cmp(node->next[1], "DOT")) /* 15 */
    {
        attri* structAttri = parseExp(node->next[0]);
        if(!structAttri) return NULL;
        if(structAttri->type == STRUCT)
        {
            mempara* tmp = ((attri*)(structAttri->ustruct.structDefMap->val))->ustructDef.memAttri;
            bool match = false;
            while(tmp)
            {
                if(strcmp(tmp->name, next2->val) == 0)
                {
                    match = true;
                    val = tmp->val;
                    break;
                }
                tmp = tmp->link;
            }
            if(!match)
            {
                printSemMsg(NON_FIELD, node->next[2]->line, next2->val);
                return NULL;
            }
        }
        else
        {   
            printSemMsg(NOT_STRUCT, node->next[0]->line, NULL);
            return NULL;
            /* TODO: process undefined structDef */
        }
    }
    else if(cmp(next0, "INT")) /* 17 */
    {
        val = intAttri;
    }
    else if(cmp(next0, "FLOAT")) /* 18 */
    {
        val = floatAttri;
    }
    else
        {assert(0);}
    return val;
}

bool parseArgs(treenode* node, mempara* paraAttri, char* funcName)
{   /* Args: Exp COMMA Args
        | Exp */
    attri* val = parseExp(node->next[0]);
    if(!val) return false;
    if(!paraAttri) 
    {
        printSemMsg(INC_ARG, node->next[0]->line, funcName);
        return false;
    }
    if(!attriEqual(val, paraAttri->val))
    {
        printSemMsg(INC_ARG, node->next[0]->line, funcName);
        return false;
    }
    else if(node->capacity == 3)
    {
        return parseArgs(node->next[2], paraAttri->link, funcName);
    }
    else if(node->capacity == 1)
    {
        if(paraAttri->link)
        {
            printSemMsg(INC_ARG, node->next[0]->line, funcName);
            return false;
        }
        else return true;
    }
}



void semanticAnalysis(treenode* root)
{
    if(root)
    {   intAttri = newBaseAttri(INT);
        floatAttri = newBaseAttri(FLOAT);
        newTable();
        assert(top != NULL);
        assert(strcmp(root->typename, "Program") == 0);
        parseProgram(root);
        deleteTable();
        freeAttri(intAttri);
        freeAttri(floatAttri);
        assert(top == NULL);
    }
}